Event Duplicate API 文档

接口功能说明

复制活动 - 基于现有活动创建新活动,同时复制关联的资源(票务、阵容、媒体等)和 Post。

[!info] 核心特性

  • 复制活动元数据(标题、时间、地点、税率等)
  • 可选复制票务产品、阵容、媒体、活动商品
  • 自动复制关联的 Post(以 Draft 状态)
  • 自动复制 StoreFront 模块配置
  • 通过 Post-Sync 框架自动同步活动变更到 Post

请求地址 & 请求方式

POST /product-event/v2/duplicate

环境说明

  • Production: https://katana-api.1m.app/product-event/v2/duplicate
  • Staging: https://staging.katana-api.1m.app/product-event/v2/duplicate

请求头(Header)

Header 必填 说明 示例
authorization Bearer Token(JWT) Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
content-type 请求内容类型 application/json
from 客户端标识 client
timezone 用户时区 Asia/Shanghai
x-track-id 追踪 ID edb43640-7349-42ad-91c7-806fc07a1954

[!warning] 认证要求

  • 必须提供有效的 JWT Token
  • 用户必须是 BUSINESS_PARTNER 角色或更高权限
  • 用户必须是源活动的所有者(curatorId 匹配)

请求参数

Body 参数

DuplicateEventRequest 继承自 CreateEventV2Request,包含所有活动创建字段,并添加复制相关字段:

必填字段

字段 类型 说明
duplicateEventId string (UUID) 要复制的源活动 ID

活动基础字段(继承自 CreateEventV2Request)

字段 类型 必填 说明
status EventStatus (enum) 活动状态:UPCOMING \ ON_HOLD \ CANCELLED \ COMPLETED
title string 活动标题
allowAutoComplete boolean 是否允许自动完成
startDateDisplay string 开始时间显示(格式:YYYY-MM-DD HH:mm
venue string 场地名称
location string 活动地点
timezone Timezone 时区配置
isTaxEnabled boolean 是否启用税费

可选字段

字段 类型 说明
endDateDisplay string 结束时间显示
locationDetails LocationDetailsDto 详细地址信息
poster ProductCoverImage 活动封面图
note string 活动备注
description string 活动描述(纯文本)
descriptionBodyJson string 活动描述(Lexical JSON)
isAddressRevealEnabled boolean 是否启用地址隐私控制
addressRevealConfig AddressRevealConfigRequest 地址隐私配置
taxConfig EventTaxConfigRequest 税费配置
platform EventPlatform 平台(PEAR \ SHOPIFY \ ALIEXPRESS
remoteId string 外部平台 ID
autoReplace boolean 是否自动替换
extraInfo Record<string, any> 平台特定元数据

可选复制字段

字段 类型 说明
tickets CreateProductV2Dto[] 要复制的票务产品列表
eventProducts CreateProductV2Dto[] 要复制的活动商品列表
lineup CreateEventLineupItemRequest[] 要复制的阵容列表
medias CreateEventMediaItemRequest[] 要复制的媒体列表

[!tip] 复制行为

  • 如果不提供 tickets/eventProducts/lineup/medias,则不复制对应资源
  • 提供空数组 [] 表示明确不复制
  • 提供 non-empty 数组则复制指定内容

响应结构 & 字段说明

响应结构为 EventLandingPageResponse,包含完整的活动配置和关联资源:

基础活动字段

字段 类型 说明
id string 新创建的活动 ID
status EventStatus 活动状态
title string 活动标题
startDateDisplay string 开始时间显示
location string 活动地点
venue string 场地名称
poster ProductCoverImage 活动封面图
timezone Timezone 时区配置
createdAt string 创建时间
creationStep EventCreationStep 创建流程步骤

税费配置

字段 类型 说明
isTaxEnabled boolean 是否启用税费
taxConfig EventTaxConfig 税费配置对象
taxConfig.customTaxRate number 自定义税率(0-100)
taxConfig.calculationType string 计算类型:CUSTOM \ AUTOMATIC

地址隐私配置

字段 类型 说明
isAddressRevealEnabled boolean 是否启用地址隐私
addressRevealConfig AddressRevealConfig 地址隐私配置
isAddressHidden boolean 地址是否隐藏

媒体配置

字段 类型 说明
medias EventMediaItemResponse[] 媒体列表
mediaTextConfig EventMediaTextConfig 媒体文本配置
mediaTextConfig.headline string 标题(默认:"Missed our last event?")
mediaTextConfig.subtitle string 副标题(默认:"Here's a lil recap...")

票务和商品

字段 类型 说明
tickets TicketProductLite[] 票务产品列表
eventProducts EventProductLite[] 活动商品列表

阵容

字段 类型 说明
lineup EventLineupItemResponse[] 阵容列表

统计数据

字段 类型 说明
statistics EventStatistics 统计数据
statistics.totalTicketsAvailable number 总可用票数
statistics.totalTicketsSold number 总已售票数
statistics.totalTicketsRedeemed number 总已核销票数

复制结果(新增字段)

字段 类型 说明
posts Post[] 复制的 Post 列表(Draft 状态)
storeFrontModules StoreFrontModule[] 复制的 StoreFront 模块配置

成功示例

请求示例

curl 'https://staging.katana-api.1m.app/product-event/v2/duplicate' \
  -H 'accept: application/json, text/plain, */*' \
  -H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
  -H 'content-type: application/json' \
  --data-raw '{
    "duplicateEventId": "3edd6be3-c7cd-48a9-9eb9-40f4dd4d04f1",
    "status": "UPCOMING",
    "title": "Event20260228 - Copy",
    "allowAutoComplete": true,
    "startDateDisplay": "2027-03-28",
    "location": "181 Taylor Avenue, Columbus, OH",
    "venue": "123",
    "timezone": {
      "timeZoneId": "America/New_York",
      "timeZoneName": "Eastern Daylight Time",
      "dstOffset": 3600,
      "rawOffset": -18000
    },
    "isTaxEnabled": true,
    "taxConfig": {
      "customTaxRate": 12,
      "calculationType": "CUSTOM"
    },
    "isAddressRevealEnabled": false,
    "poster": {
      "src": "https://res.cloudinary.com/dr9io1zjv/v1762842604/uploaded_images/s47jfs33fgz3do4wvrxz.png",
      "width": 3072,
      "height": 2048,
      "position": 0,
      "mediaType": "IMAGE"
    },
    "platform": "PEAR",
    "tickets": [{
      "title": "General Admission1",
      "listingType": "TICKET",
      "eventId": "3edd6be3-c7cd-48a9-9eb9-40f4dd4d04f1",
      "variants": [{
        "option1": "Default Title",
        "price": "0",
        "inventoryQuantity": 10,
        "inventoryPolicy": "DENY",
        "taxable": true
      }]
    }]
  }'

响应示例

{
  "id": "new-event-uuid",
  "status": "UPCOMING",
  "title": "Event20260228 - Copy",
  "startDateDisplay": "2027-03-28",
  "location": "181 Taylor Avenue, Columbus, OH",
  "venue": "123",
  "poster": {
    "src": "https://res.cloudinary.com/dr9io1zjv/v1762842604/uploaded_images/s47jfs33fgz3do4wvrxz.png",
    "width": 3072,
    "height": 2048,
    "mediaType": "IMAGE"
  },
  "timezone": {
    "timeZoneId": "America/New_York",
    "timeZoneName": "Eastern Daylight Time"
  },
  "isTaxEnabled": true,
  "taxConfig": {
    "customTaxRate": 12,
    "calculationType": "CUSTOM"
  },
  "creationStep": "CHOOSE_MODULES",
  "medias": [...],
  "tickets": [{
    "id": "new-ticket-uuid",
    "title": "General Admission1",
    "listingType": "TICKET",
    "status": "ACTIVE",
    "inventoryQuantity": 10,
    "soldQuantity": 0,
    "variants": [...]
  }],
  "lineup": [],
  "eventProducts": [],
  "statistics": {
    "totalTicketsAvailable": 10,
    "totalTicketsSold": 0,
    "totalTicketsRedeemed": 0
  },
  "posts": [{
    "id": "new-post-uuid",
    "status": "DRAFT",
    "createFromEventId": "new-event-uuid",
    "syncEnabled": true,
    "headline": "Event20260228 - Copy",
    "relatedProducts": ["new-ticket-uuid"]
  }],
  "storeFrontModules": [...],
  "messageInfo": {
    "smsMaxLimit": 3,
    "emailMaxLimit": 5,
    "cooldownMinutes": 15
  }
}

错误示例

401 - 未授权

{
  "statusCode": 401,
  "message": "Unauthorized",
  "error": "Unauthorized"
}

原因

  • JWT Token 无效或过期
  • Token 缺失

403 - 禁止访问

{
  "statusCode": 403,
  "message": "You do not have permission to duplicate this event",
  "error": "Forbidden"
}

原因

  • 用户不是源活动的所有者
  • 用户权限不足(非 BUSINESS_PARTNER 角色)

404 - 活动不存在

{
  "statusCode": 404,
  "message": "Event not found",
  "error": "Not Found"
}

原因

  • duplicateEventId 对应的活动不存在
  • 活动已被软删除

400 - 请求参数错误

{
  "statusCode": 400,
  "message": [
    "duplicateEventId must be a UUID",
    "startDateDisplay must be in YYYY-MM-DD HH:mm format"
  ],
  "error": "Bad Request"
}

原因

  • 请求参数验证失败
  • 字段类型或格式不正确

500 - 服务器内部错误

{
  "statusCode": 500,
  "message": "Failed to duplicate event",
  "error": "Internal Server Error"
}

原因

  • 数据库事务失败
  • Post 创建失败(非阻塞,活动仍会创建)
  • StoreFront 模块复制失败

注意事项 & 业务逻辑

复制流程

flowchart TD
    A[开始: POST /duplicate] --> B[验证用户权限]
    B --> C[查询源活动和关联数据]
    C --> D[创建 DuplicateContext]
    D --> E[开始事务]
    E --> F[Stage 1: 创建新活动]
    F --> G[Stage 2: 并行创建资源]
    G --> H{创建成功?}
    H -->|是| I[Stage 3: 复制 Posts]
    H -->|否| J[回滚事务]
    I --> K[Stage 4: 复制 StoreFront 模块]
    J --> L[抛出异常]
    K --> M[提交事务]
    M --> N[发布事件同步]
    N --> O[返回完整响应]

Stage 1: 创建活动

  • 使用 doCreateEvent() 创建新活动
  • 新活动自动设置 creationStep = 'CHOOSE_MODULES'
  • 从请求中获取的活动字段应用于新活动

Stage 2: 并行创建资源

四个资源类型并行创建(使用 Promise.all):

资源类型 方法 说明
Lineups batchCreateLineups() 复制阵容列表
Medias batchCreateMedia() 复制媒体文件
Tickets batchCreateTickets() 复制票务产品
Event Products batchCreateEventProducts() 复制活动商品

Stage 3: 复制 Posts

  • 查询源活动的所有 Posts(status: ACTIVE
  • 为每个 Post 创建副本,状态设为 DRAFT
  • 更新 Post 中的 createFromEventId 指向新活动
  • 更新 Post 中的 relatedProducts 指向新票务 ID
  • 保留用户自定义的 headlineallowCustomizeDisplayPrices

Stage 4: 复制 StoreFront 模块

  • 查询源 Posts 的 StoreFront 模块配置
  • 为新 Posts 创建相同的模块配置
  • 保持模块顺序和参数一致

Post-Sync 集成

  • 活动复制完成后,通过 EventSyncHelper 发布事件
  • 触发 Post-Sync 框架自动同步活动变更到 Post
  • 新 Posts 继承源 Posts 的 syncEnabled 属性

回滚机制

  • 任何阶段失败都会触发回滚
  • rollbackDuplicate() 清理已创建的资源
  • 源活动不受影响

性能追踪

  • 使用 perfTracker 追踪操作性能
  • 记录数据库操作次数
  • 记录变更类型(event, lineup, media, ticket, event_product, storefront)

业务规则

所有权验证

  • 用户必须是源活动的 curatorId
  • 只能复制自己拥有的活动
  • Admin 用户可复制任何活动(通过 Admin API)

状态继承

  • 新活动的 creationStep 自动设置为 CHOOSE_MODULES
  • 新活动的 status 由请求参数决定
  • 复制的票务产品保持 LIFECYCLE_STATUS_NORMAL

ID 映射

// 票务产品 ID 映射
copyFromProductIdToProductId: Map<string, string>
productIdToCopyFromProductId: Map<string, string>

用于在复制过程中跟踪旧 ID 到新 ID 的映射关系。

非阻塞设计

  • Post 创建失败不会导致活动复制失败
  • StoreFront 模块复制失败不会导致活动复制失败
  • 只有活动自身创建失败才会回滚整个操作

相关接口

获取活动配置

GET /product-event/v2/:eventId/config

返回活动的完整配置,用于前端填充复制表单。

获取活动重复配置

GET /product-event/v2/:eventId/duplicate-config

返回活动复制的特殊配置(如票务、阵容等)。

创建活动

POST /product-event/v2

创建新活动,不基于现有活动。


参考资料

  • JIRA: KAT-8883 (Event Duplication)
  • JIRA: KAT-8857 (Post Integration)
  • JIRA: KAT-10547 (Post Sync on Duplicate)
  • 源码: src/product-event/services/event.duplicate.service.ts
  • DTO: src/product-event/v2/dto/event-v2.dto.ts
  • 相关文档: src/product-event/CLAUDE.md

results matching ""

    No results matching ""